/*
*  This file is part of ygg-brute
*  Copyright (c) ygg-brute authors
*  See LICENSE for licensing information
*/

#pragma once

#include <list>
#include <thread>
#include <chrono>
#include <condition_variable>

#include "result.hpp"
#include "stats.hpp"
#include "io.hpp"

#define DECLSPEC

#include "generic/basepoint_mul_fold_table_2526_def.h"
#include "generic/field.h"
#include "generic/point.h"
#include "generic/scalar_mul.h"

#undef DECLSPEC

static AffineNielsPoint baseopint_mul_fold_table[256] = ED25519_BASEPOINT_MUL_FOLD_TABLE_2526_DEF;

inline void compute_pkey(uint8_t pkey[32], const uint8_t skey[32])
{
    // TODO : get rid of the cast
    EdwardsPoint p;
    scalar_mul_folds(&p, reinterpret_cast<const uint32_t*>(skey), baseopint_mul_fold_table);
    point_to_montgomery_bytes(&p, pkey);
}

class Outputter
{
public:
    Outputter()
    : thread_{&Outputter::run, this}
    {}

    ~Outputter() { stop(); }

    void stop() {
        if(stop_) return;

        {
            std::lock_guard<std::mutex> lock{mutex_};
            stop_ = true;
        }

        cv_.notify_one();
        thread_.join();
    }

    void output(std::list<Result> results, const Stats& stats)
    {
        if(stop_) return;

        bool notify{false};

        {
            std::lock_guard<std::mutex> lock{mutex_};
            total_stats_.merge(stats);
            interval_stats_.merge(stats);
            results_.splice(results_.end(), std::move(results));
            notify = results_.size() > 0;
        }

        if(notify) cv_.notify_one();
    }

private:
    using clock_t = std::chrono::steady_clock;

    static constexpr std::chrono::seconds stats_interval{10};
    static constexpr unsigned total_stats_frequency{5};

    void run()
    {
        const auto is_ready = [this] { return stop_ || !results_.empty(); };

        const auto start_ts = clock_t::now();
        auto last_stats_ts = start_ts;
        unsigned stats_shown{0};

        std::unique_lock<std::mutex> lock{mutex_};
        for(;;) {
            if(!is_ready())
                cv_.wait_until(lock, last_stats_ts + stats_interval, is_ready);

            if(stop_) return;

            Stats total_stats, interval_stats;

            auto now = clock_t::now();
            auto interval = now - last_stats_ts;
            bool show_stat = interval >= stats_interval;
            auto results = std::exchange(results_, {});

            if(show_stat) {
                total_stats = total_stats_;
                interval_stats = std::exchange(interval_stats_, {});
                last_stats_ts = now;
            }

            lock.unlock();

            uint8_t pkey[32];

            for(auto& result : results) {
                compute_pkey(pkey, result.skey);
                std::cout << "# Address: " << result.address << "\n";
                std::cout << "EncryptionPrivateKey: ";
                out_hex_str(std::cout, result.skey, 32);
                std::cout << "\n";
                std::cout << "EncryptionPublicKey: ";
                out_hex_str(std::cout, pkey, 32);
                std::cout << "\n";
            }
            std::cout << std::flush;

            if(show_stat) {
                auto print_stat = [&](auto stat, auto interval) {
                    constexpr uint64_t us_per_s = 1000000;
                    auto interval_us = std::chrono::duration_cast<std::chrono::microseconds>(interval);
                    std::cerr << "found " << stat.found << ", processed " << stat.processed << " keys in ";
                    std::cerr << (interval_us.count() / us_per_s)  << " seconds ";
                    std::cerr << "(" << (stat.processed / (double(interval_us.count()) / 1000)) << " KKeys/s)";
                    std::cerr << "; max hi " << stat.max;
                    std::cerr << std::endl;
                };

                print_stat(interval_stats, interval);
                if(++stats_shown >= total_stats_frequency) {
                    stats_shown = 0;
                    std::cerr << "Total: ";
                    print_stat(total_stats, now - start_ts);
                }
            }

            lock.lock();
    }
    }

private:
    Stats total_stats_;
    Stats interval_stats_;
    std::list<Result> results_;

    std::condition_variable cv_;
    std::mutex mutex_;
    std::thread thread_;

    bool stop_{false};
};